#include "CRL_Camera.h"
#include "CBL_Math.h"

CB_SUBNAMESPACE_START(RenderLib)

CCamera::CCamera() 
: m_Position(0,0,0)
, m_Direction(0,0,-1)
, m_UpVector(0,0,1)
, m_Near(1)
, m_Far(10000)
, m_MatrixStyle(OpenGL)
{
	SetFov(90);
	UpdateMatrices();
}

// -----------------------------------------------------------------------------------

CCamera::CCamera(EMatrixStyle _MatrixStyle) 
	: m_Position(0,0,0)
	, m_Direction(0,0,-1)
	, m_UpVector(0,0,1)
	, m_Near(1)
	, m_Far(10000)
	, m_MatrixStyle(_MatrixStyle)
{
	SetFov(90);
	UpdateMatrices();
}

// -----------------------------------------------------------------------------------

CCamera::CCamera(EMatrixStyle _MatrixStyle, float _Fov, float _Near, float _Far) 
	: m_Position(0,0,0)
	, m_Direction(0,0,-1)
	, m_UpVector(0,0,1)
	, m_Near(_Near)
	, m_Far(_Far) 
	, m_MatrixStyle(_MatrixStyle)
{
	SetFov(_Fov);
	UpdateMatrices();
}

// -----------------------------------------------------------------------------------

CCamera::CCamera(EMatrixStyle _MatrixStyle, float _Left, float _Right, float _Top, float _Bottom, float _Near, float _Far) 
	: m_Position(0,0,0)
	, m_Direction(0,0,-1)
	, m_UpVector(0,0,1)
	, m_Near(_Near)
	, m_Far(_Far)
	, m_MatrixStyle(_MatrixStyle)
{
	SetPlane(_Left, _Right, _Top, _Bottom);
	UpdateMatrices();
}

// -----------------------------------------------------------------------------------

void CCamera::SetFov(float _DegreesX, float _DegreesY)
{
	m_FovX = _DegreesX;
	m_FovY = _DegreesY;
	
	m_Right = tan(Math::Rad(m_FovX / 2.0f)) * m_Near;
	m_Left  = -m_Right;
				  
	m_Top    = tan(Math::Rad(m_FovY / 2.0f)) * m_Near;
	m_Bottom = -m_Top;
}

// -----------------------------------------------------------------------------------

void CCamera::SetPlane(float _Left, float _Right, float _Top, float _Bottom)
{
	m_Left   = _Left;
	m_Right  = _Right;
	m_Top    = _Top;
	m_Bottom = _Bottom;
	
	SetNear(m_Near);
}

// -----------------------------------------------------------------------------------

void CCamera::SetFrustum(float _Left, float _Right, float _Top, float _Bottom, float _Near, float _Far)
{
	m_Near = _Near;
	m_Far = _Far;
	
	SetPlane(_Left, _Right, _Top, _Bottom);
}

// -----------------------------------------------------------------------------------

void CCamera::SetNear(float _Near)
{
	m_Near = _Near;
	
	m_FovX = Math::Deg(atan(m_Right / m_Near)) - Math::Deg(atan(m_Left / m_Near));
	m_FovY = Math::Deg(atan(m_Top / m_Near)) - Math::Deg(atan(m_Bottom / m_Near));
}

// -----------------------------------------------------------------------------------

void CCamera::SetFar(float _Far)
{
	m_Far = _Far;
}

// -----------------------------------------------------------------------------------

void CCamera::UpdateMatrices()
{
	m_View = GenerateViewMatrix();
	m_Projection = GenerateProjectionMatrix();
	m_ViewProjection = m_View * m_Projection;
}

// -----------------------------------------------------------------------------------

Math::Float4x4 CCamera::GenerateViewMatrix() const
{
	Math::Float4x4 View;
	
	Math::Float3 Right = Math::Cross(m_Direction, m_UpVector);
	Math::Float3 RealUp = Math::Cross(Right, m_Direction);
	
	View[0][0] = Right[0];		View[0][1] = RealUp[0];		View[0][2] = m_Direction[0];
	View[1][0] = Right[1];		View[1][1] = RealUp[1];		View[1][2] = m_Direction[1];
	View[2][0] = Right[2];		View[2][1] = RealUp[2];		View[2][2] = m_Direction[2];
	View[3][0] = m_Position[0];	View[2][1] = m_Position[1];	View[2][2] = m_Position[2];
	
	return View;
}

// -----------------------------------------------------------------------------------

Math::Float4x4 CCamera::GenerateProjectionMatrix() const
{
	Math::Float4x4 Projection;
	
	switch (m_MatrixStyle)
	{
	case OpenGL:
		Projection[0][0] = 2 * m_Near / (m_Right - m_Left); 
		Projection[1][0] = 0;
		Projection[2][0] = 0;
		Projection[3][0] = 0;
		
		Projection[0][1] = 0; 
		Projection[1][1] = 2 * m_Near / (m_Top - m_Bottom);
		Projection[2][1] = 0;
		Projection[3][1] = 0;
		
		Projection[0][2] = (m_Right + m_Left) / (m_Right - m_Left); 
		Projection[1][2] = (m_Top + m_Bottom) / (m_Top - m_Bottom);
		Projection[2][2] = -((m_Far + m_Near) / (m_Far - m_Near));
		Projection[3][2] = -1;
		
		Projection[0][3] = 0; 
		Projection[1][3] = 0;
		Projection[2][3] = -((2 * m_Far * m_Near) / (m_Far - m_Near));
		Projection[3][3] = 0;
		break;
			
	case OpenGLOrtho:
		Projection[0][0] = 2 / (m_Right - m_Left); 
		Projection[1][0] = 0;
		Projection[2][0] = 0;
		Projection[3][0] = 0;
		
		Projection[0][1] = 0; 
		Projection[1][1] = 2 / (m_Top - m_Bottom);
		Projection[2][1] = 0;
		Projection[3][1] = 0;
		
		Projection[0][2] = 0; 
		Projection[1][2] = 0;
		Projection[2][2] = 2 / (m_Far - m_Near);
		Projection[3][2] = 0;
		
		Projection[0][3] = -((m_Right + m_Left) / (m_Right - m_Left)); 
		Projection[1][3] = -((m_Top + m_Bottom) / (m_Top - m_Bottom));
		Projection[2][3] = -((m_Far + m_Near) / (m_Far - m_Near));;
		Projection[3][3] = 1;
		break;
			
	case DirectX:
		Projection[0][0] = 2 * m_Near / (m_Right - m_Left); 
		Projection[1][0] = 0;
		Projection[2][0] = 0;
		Projection[3][0] = 0;
		
		Projection[0][1] = 0; 
		Projection[1][1] = 2 * m_Near / (m_Top - m_Bottom);
		Projection[2][1] = 0;
		Projection[3][1] = 0;
		
		Projection[0][2] = (m_Right + m_Left) / (m_Right - m_Left); 
		Projection[1][2] = (m_Top + m_Bottom) / (m_Top - m_Bottom);
		Projection[2][2] = m_Far / (m_Far - m_Near);
		Projection[3][2] = 1;
		
		Projection[0][3] = 0; 
		Projection[1][3] = 0;
		Projection[2][3] = -((m_Far * m_Near) / (m_Far - m_Near));;
		Projection[3][3] = 0;
		break;
	
	case DirectXOrtho:
		Projection[0][0] = 2 / (m_Right - m_Left); 
		Projection[1][0] = 0;
		Projection[2][0] = 0;
		Projection[3][0] = 0;
		
		Projection[0][1] = 0; 
		Projection[1][1] = 2 / (m_Top - m_Bottom);
		Projection[2][1] = 0;
		Projection[3][1] = 0;
		
		Projection[0][2] = 0; 
		Projection[1][2] = 0;
		Projection[2][2] = 1 / (m_Far - m_Near);
		Projection[3][2] = 0;
		
		Projection[0][3] = -((m_Right + m_Left) / (m_Right - m_Left)); 
		Projection[1][3] = -((m_Top + m_Bottom) / (m_Top - m_Bottom));
		Projection[2][3] = -(m_Near / (m_Far - m_Near));;
		Projection[3][3] = 1;
		break;
	}
	
	return Projection;
}

CB_SUBNAMESPACE_END